﻿//
// $HeadURL: http://codeka-memcache.googlecode.com/svn/trunk/Codeka.Memcache/Config/DefaultConfiguration.cs $
// $Revision: 2 $
// $Author: dean.codeka $     $Date: 2010-02-13 11:41:50 +0000 (Sat, 13 Feb 2010) $
//
// Copyright © 2009-2010 Dean Harding, all rights reserved.
//

using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System;

namespace Codeka.Memcache.Config
{
    /// <summary>
    /// The default implementation of <see cref="IMemcacheConfiguration"/> gets
    /// it's configuration data from the web.config (or app.config) file.
    /// </summary>
    /// <remarks>
    /// <para>This <see cref="IMemcacheConfiguration"/> instance gets it's configuration
    /// data from the web.config file. </para>
    /// </remarks>
    public class DefaultConfiguration : IMemcacheConfiguration
    {
        private MemcacheClientConfigurationSection configSection;
        private List<ServerConfig> servers;

        /// <summary>
        /// Constructs a new <see cref="DefaultConfiguration"/>.
        /// </summary>
        public DefaultConfiguration()
            : this((MemcacheClientConfigurationSection)ConfigurationManager.GetSection("memcache"))
        {
        }

        /// <summary>
        /// Constructs a new <see cref="DefaultConfiguration"/> that'll use the specified instance
        /// of <see cref="MemcacheClientConfigurationSection"/> for getting the configuration data.
        /// </summary>
        public DefaultConfiguration(MemcacheClientConfigurationSection configSection)
        {
            this.configSection = configSection;
        }

        /// <summary>
        /// Gets the collection of <see cref="ServerConfig"/>s representing all the
        /// servers we'll be connecting to.
        /// </summary>
        public IEnumerable<ServerConfig> GetServers()
        {
            // We cache the servers because it might be expensive to construct the
            // list (especially if you've specified domain names instead of IP addresses)
            if (servers == null)
            {
                servers = new List<ServerConfig>(
                    from ServerConfigurationElement svr in configSection.Servers
                    select new ServerConfig
                    {
                        EndPoint = ParseAddress(svr.Address),
                        Weight = svr.Weight,
                    });
            }

            return servers;
        }

        /// <summary>
        /// Parses an "address" string into an <see cref="IPEndPoint"/>.
        /// </summary>
        private static IPEndPoint ParseAddress(string address)
        {
            if (address.StartsWith("["))
            {
                // if it's an IPv6 address, we'll do it a little differently...
                int endAddress = address.IndexOf(']');
                if (endAddress > 0 && address[endAddress + 1] == ':')
                {
                    int port = int.Parse(address.Substring(endAddress + 2));
                    var addr = IPAddress.Parse(address.Substring(0, endAddress + 1));
                    return new IPEndPoint(addr, port);
                }

                throw new ArgumentException("Invalid IPv6 endpoint: " + address);
            }
            else
            {
                var parts = address.Split(":".ToCharArray(), 2);
                int port = 11211;
                if (parts.Length == 2)
                    port = int.Parse(parts[1]);

                IPAddress addr;
                if (!IPAddress.TryParse(parts[0], out addr))
                {
                    // try to resolve it as a domain name...
                    addr = Dns.GetHostAddresses(parts[0]).FirstOrDefault();
                }

                return new IPEndPoint(addr, port);
            }
        }
    }

    /// <summary>
    /// This is the <see cref="ConfigurationSection"/> instance that we load from the
    /// web.config file.
    /// </summary>
    public class MemcacheClientConfigurationSection : ConfigurationSection
    {
        /// <summary>
        /// Gets the <see cref="ServerElementCollection"/> configuration section that we contain.
        /// </summary>
        [ConfigurationProperty("servers")]
        public ServerElementCollection Servers
        {
            get { return (ServerElementCollection)this["servers"]; }
        }
    }

    /// <summary>
    /// Represents the collection of <see cref="ServerElement"/>s in the configuration file.
    /// </summary>
    public class ServerElementCollection : ConfigurationElementCollection
    {
        /// <summary>
        /// Constructs a new <see cref="ServerConfigurationElement"/> and returns it, ready to be populated.
        /// </summary>
        /// <returns>A new <see cref="ServerConfigurationElement"/>, ready to be populated.</returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new ServerConfigurationElement();
        }

        /// <summary>
        /// Gets the "key" for the given <see cref="ConfigurationElement"/>, which we assume
        /// is a <see cref="ServerConfigurationElement"/>.
        /// </summary>
        /// <param name="element">The <see cref="ServerConfigurationElement"/> we need the key for.</param>
        /// <returns>The key of the given element.</returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((ServerConfigurationElement)element).Address;
        }
    }

    /// <summary>
    /// Represents a single server element configuration.
    /// </summary>
    public class ServerConfigurationElement : ConfigurationElement
    {
        /// <summary>
        /// Gets or sets a string representing the actual address of this server.
        /// </summary>
        [ConfigurationProperty("address", IsRequired = true, IsKey = true)]
        public string Address
        {
            get { return (string)base["address"]; }
            set { base["address"] = value; }
        }

        /// <summary>
        /// Gets or sets the weight of this server.
        /// </summary>
        [ConfigurationProperty("weight", DefaultValue=100)]
        public int Weight
        {
            get { return (int)base["weight"]; }
            set { base["weight"] = value; }
        }
    }
}
